package com.ldy.seal.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.security.GeneralSecurityException;
import java.util.HashMap;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfDate;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignature;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.ldy.seal.vo.SealInfo;
import com.itextpdf.text.pdf.security.PdfPKCS7;

/**
 * ukey 签章
 * 
 * <pre>
 *   ukey 签章，需要使用ukey做签名，每一个印章的签名都需要与客户端交互
 *   把pdf内容通过hash算法返回给客户端，客户端在通过ukey私钥对内容签名
 *   签名后结果传回给服务器完成签章。
 *   包含 2个对外方法 签章前 + 完成签章
 * </pre>
 * 
 * @author lindeyi
 *
 */
public class UKeySealUtil {

	/**
	 * 签章前，获取待签名的pdf摘要
	 * 
	 * @param inputStream 需要签章的pdf 输入流
	 * @param bos         输出流
	 * @param pkcs        pkcs7
	 * @param signImage   签章图片
	 * @param sealInfo    签章位置
	 * @param reason      签订原因
	 * @param location    签订地址
	 * @throws GeneralSecurityException 
	 * @throws IOException 
	 * @throws DocumentException 
	 * @return PdfSignatureAppearance
	 */
	public static PdfSignatureAppearance beforeSeal(InputStream inputStream, //
			ByteArrayOutputStream bos, PdfPKCS7 pkcs, Image signImage, //
			SealInfo sealInfo, //
			String reason, //
			String location) //
			throws GeneralSecurityException, IOException, DocumentException {
		// 读取pdf内容
		PdfReader pdfReader = new PdfReader(inputStream);

		int pageNum = pdfReader.getNumberOfPages();

		if (null == sealInfo) {
			return null;
		}
		if (sealInfo.getPage() > pageNum) {
			return null;
		}
		// pdf 签章设置
		PdfSignatureAppearance appearance = PDFUtil.setPdfSign(pdfReader, null, sealInfo, signImage, reason, location);

		// 写入输出流中
		bos.write(getPDFHash(appearance, pkcs));

		return appearance;

	}

	/**
	 * 完成签章
	 * 
	 * @param sap 
	 * @param sgn 
	 * @param hash 
	 * @param extSignature 
	 * @return ByteArrayOutputStream
	 * @throws Exception 
	 */
	public static ByteArrayOutputStream finishSeal(PdfSignatureAppearance sap, PdfPKCS7 sgn, byte[] hash,
			byte[] extSignature) throws Exception {
		int estimatedSize = 8192;
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		sgn.setExternalDigest(extSignature, null, "RSA");

		byte[] encodedSig = sgn.getEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);

		if ((estimatedSize < encodedSig.length)) {
			throw new IOException("Not enough space");
		}

		byte[] paddedSig = new byte[estimatedSize];
		System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);

		PdfDictionary dic2 = new PdfDictionary();
		dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
		Method method = sap.getClass().getDeclaredMethod("setOriginalout", OutputStream.class);
		method.setAccessible(true);
		method.invoke(sap, os);
		sap.close(dic2);
		return os;
	}

	/**
	 * 获取PDFhash
	 * 
	 * @param sap 
	 * @param sgn 
	 * @return byte[]
	 * @throws IOException 
	 * @throws DocumentException 
	 * @throws GeneralSecurityException 
	 */
	private static byte[] getPDFHash(PdfSignatureAppearance sap, PdfPKCS7 sgn)
			throws IOException, DocumentException, GeneralSecurityException {
		int estimatedSize = 8192;
		ExternalDigest digest = new BouncyCastleDigest();
		// 签名证书
		sap.setCertificate(sgn.getSigningCertificate());

		PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
		dic.setReason(sap.getReason());
		dic.setLocation(sap.getLocation());
		dic.setSignatureCreator(sap.getSignatureCreator());
		dic.setContact(sap.getContact());
		dic.setDate(new PdfDate(sap.getSignDate()));
		sap.setCryptoDictionary(dic);

		HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
		exc.put(PdfName.CONTENTS, new Integer(estimatedSize * 2 + 2));
		sap.preClose(exc);

		String hashAlgorithm = "SHA1";
		InputStream data = sap.getRangeStream();
		byte[] hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));

		return hash;
	}
}
